//===-- FormatManager.cpp -------------------------------------------*- C++ -*-===//
//
//                     The LLVM Compiler Infrastructure
//
// This file is distributed under the University of Illinois Open Source
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//

#include "lldb/DataFormatters/FormatManager.h"

#include "llvm/ADT/STLExtras.h"

// C Includes
// C++ Includes
// Other libraries and framework includes
// Project includes

#include "lldb/Core/Debugger.h"
#include "lldb/Core/Log.h"
#include "lldb/DataFormatters/FormattersHelpers.h"
#include "lldb/DataFormatters/LanguageCategory.h"
#include "Plugins/ScriptInterpreter/Python/lldb-python.h"
#include "lldb/Target/ExecutionContext.h"
#include "lldb/Target/Language.h"

using namespace lldb;
using namespace lldb_private;
using namespace lldb_private::formatters;

struct FormatInfo
{
    Format format;
    const char format_char; // One or more format characters that can be used for this format.
    const char *format_name;    // Long format name that can be used to specify the current format
};

static FormatInfo 
g_format_infos[] = 
{
    { eFormatDefault        , '\0'  , "default"             },
    { eFormatBoolean        , 'B'   , "boolean"             },
    { eFormatBinary         , 'b'   , "binary"              },
    { eFormatBytes          , 'y'   , "bytes"               },
    { eFormatBytesWithASCII , 'Y'   , "bytes with ASCII"    },
    { eFormatChar           , 'c'   , "character"           },
    { eFormatCharPrintable  , 'C'   , "printable character" },
    { eFormatComplexFloat   , 'F'   , "complex float"       },
    { eFormatCString        , 's'   , "c-string"            },
    { eFormatDecimal        , 'd'   , "decimal"             },
    { eFormatEnum           , 'E'   , "enumeration"         },
    { eFormatHex            , 'x'   , "hex"                 },
    { eFormatHexUppercase   , 'X'   , "uppercase hex"       },
    { eFormatFloat          , 'f'   , "float"               },
    { eFormatOctal          , 'o'   , "octal"               },
    { eFormatOSType         , 'O'   , "OSType"              },
    { eFormatUnicode16      , 'U'   , "unicode16"           },
    { eFormatUnicode32      , '\0'  , "unicode32"           },
    { eFormatUnsigned       , 'u'   , "unsigned decimal"    },
    { eFormatPointer        , 'p'   , "pointer"             },
    { eFormatVectorOfChar   , '\0'  , "char[]"              },
    { eFormatVectorOfSInt8  , '\0'  , "int8_t[]"            },
    { eFormatVectorOfUInt8  , '\0'  , "uint8_t[]"           },
    { eFormatVectorOfSInt16 , '\0'  , "int16_t[]"           },
    { eFormatVectorOfUInt16 , '\0'  , "uint16_t[]"          },
    { eFormatVectorOfSInt32 , '\0'  , "int32_t[]"           },
    { eFormatVectorOfUInt32 , '\0'  , "uint32_t[]"          },
    { eFormatVectorOfSInt64 , '\0'  , "int64_t[]"           },
    { eFormatVectorOfUInt64 , '\0'  , "uint64_t[]"          },
    { eFormatVectorOfFloat16, '\0'  , "float16[]"           },
    { eFormatVectorOfFloat32, '\0'  , "float32[]"           },
    { eFormatVectorOfFloat64, '\0'  , "float64[]"           },
    { eFormatVectorOfUInt128, '\0'  , "uint128_t[]"         },
    { eFormatComplexInteger , 'I'   , "complex integer"     },
    { eFormatCharArray      , 'a'   , "character array"     },
    { eFormatAddressInfo    , 'A'   , "address"             },
    { eFormatHexFloat       , '\0'  , "hex float"           },
    { eFormatInstruction    , 'i'   , "instruction"         },
    { eFormatVoid           , 'v'   , "void"                }
};

static uint32_t g_num_format_infos = llvm::array_lengthof(g_format_infos);

static bool
GetFormatFromFormatChar (char format_char, Format &format)
{
    for (uint32_t i=0; i<g_num_format_infos; ++i)
    {
        if (g_format_infos[i].format_char == format_char)
        {
            format = g_format_infos[i].format;
            return true;
        }
    }
    format = eFormatInvalid;
    return false;
}

static bool
GetFormatFromFormatName (const char *format_name, bool partial_match_ok, Format &format)
{
    uint32_t i;
    for (i=0; i<g_num_format_infos; ++i)
    {
        if (strcasecmp (g_format_infos[i].format_name, format_name) == 0)
        {
            format = g_format_infos[i].format;
            return true;
        }
    }
    
    if (partial_match_ok)
    {
        for (i=0; i<g_num_format_infos; ++i)
        {
            if (strcasestr (g_format_infos[i].format_name, format_name) == g_format_infos[i].format_name)
            {
                format = g_format_infos[i].format;
                return true;
            }
        }
    }
    format = eFormatInvalid;
    return false;
}

void
FormatManager::Changed ()
{
    ++m_last_revision;
    m_format_cache.Clear ();
    Mutex::Locker lang_locker(m_language_categories_mutex);
    for (auto& iter : m_language_categories_map)
    {
        if (iter.second)
            iter.second->GetFormatCache().Clear();
    }
}

bool
FormatManager::GetFormatFromCString (const char *format_cstr,
                                     bool partial_match_ok,
                                     lldb::Format &format)
{
    bool success = false;
    if (format_cstr && format_cstr[0])
    {
        if (format_cstr[1] == '\0')
        {
            success = GetFormatFromFormatChar (format_cstr[0], format);
            if (success)
                return true;
        }
        
        success = GetFormatFromFormatName (format_cstr, partial_match_ok, format);
    }
    if (!success)
        format = eFormatInvalid;
    return success;
}

char
FormatManager::GetFormatAsFormatChar (lldb::Format format)
{
    for (uint32_t i=0; i<g_num_format_infos; ++i)
    {
        if (g_format_infos[i].format == format)
            return g_format_infos[i].format_char;
    }
    return '\0';
}

const char *
FormatManager::GetFormatAsCString (Format format)
{
    if (format >= eFormatDefault && format < kNumFormats)
        return g_format_infos[format].format_name;
    return NULL;
}

void
FormatManager::EnableAllCategories ()
{
    m_categories_map.EnableAllCategories ();
    Mutex::Locker lang_locker(m_language_categories_mutex);
    for (auto& iter : m_language_categories_map)
    {
        if (iter.second)
            iter.second->Enable();
    }
}

void
FormatManager::DisableAllCategories ()
{
    m_categories_map.DisableAllCategories ();
    Mutex::Locker lang_locker(m_language_categories_mutex);
    for (auto& iter : m_language_categories_map)
    {
        if (iter.second)
            iter.second->Disable();
    }
}

void
FormatManager::GetPossibleMatches (ValueObject& valobj,
                                   CompilerType compiler_type,
                                   uint32_t reason,
                                   lldb::DynamicValueType use_dynamic,
                                   FormattersMatchVector& entries,
                                   bool did_strip_ptr,
                                   bool did_strip_ref,
                                   bool did_strip_typedef,
                                   bool root_level)
{
    compiler_type = compiler_type.GetTypeForFormatters();
    ConstString type_name(compiler_type.GetConstTypeName());
    if (valobj.GetBitfieldBitSize() > 0)
    {
        StreamString sstring;
        sstring.Printf("%s:%d",type_name.AsCString(),valobj.GetBitfieldBitSize());
        ConstString bitfieldname = ConstString(sstring.GetData());
        entries.push_back({bitfieldname,0,did_strip_ptr,did_strip_ref,did_strip_typedef});
        reason |= lldb_private::eFormatterChoiceCriterionStrippedBitField;
    }

    if (!compiler_type.IsMeaninglessWithoutDynamicResolution())
    {
        entries.push_back({type_name,reason,did_strip_ptr,did_strip_ref,did_strip_typedef});

        ConstString display_type_name(compiler_type.GetDisplayTypeName());
        if (display_type_name != type_name)
            entries.push_back({display_type_name,reason,did_strip_ptr,did_strip_ref,did_strip_typedef});
    }

    for (bool is_rvalue_ref = true, j = true; j && compiler_type.IsReferenceType(nullptr, &is_rvalue_ref); j = false)
    {
        CompilerType non_ref_type = compiler_type.GetNonReferenceType();
        GetPossibleMatches(valobj,
                           non_ref_type,
                           reason | lldb_private::eFormatterChoiceCriterionStrippedPointerReference,
                           use_dynamic,
                           entries,
                           did_strip_ptr,
                           true,
                           did_strip_typedef);
        if (non_ref_type.IsTypedefType())
        {
            CompilerType deffed_referenced_type = non_ref_type.GetTypedefedType();
            deffed_referenced_type = is_rvalue_ref ? deffed_referenced_type.GetRValueReferenceType() : deffed_referenced_type.GetLValueReferenceType();
            GetPossibleMatches(valobj,
                               deffed_referenced_type,
                               reason | lldb_private::eFormatterChoiceCriterionNavigatedTypedefs,
                               use_dynamic,
                               entries,
                               did_strip_ptr,
                               did_strip_ref,
                               true); // this is not exactly the usual meaning of stripping typedefs
        }
    }
    
    if (compiler_type.IsPointerType())
    {
        CompilerType non_ptr_type = compiler_type.GetPointeeType();
        GetPossibleMatches(valobj,
                           non_ptr_type,
                           reason | lldb_private::eFormatterChoiceCriterionStrippedPointerReference,
                           use_dynamic,
                           entries,
                           true,
                           did_strip_ref,
                           did_strip_typedef);
        if (non_ptr_type.IsTypedefType())
        {
            CompilerType deffed_pointed_type = non_ptr_type.GetTypedefedType().GetPointerType();
            GetPossibleMatches(valobj,
                               deffed_pointed_type,
                               reason | lldb_private::eFormatterChoiceCriterionNavigatedTypedefs,
                               use_dynamic,
                               entries,
                               did_strip_ptr,
                               did_strip_ref,
                               true); // this is not exactly the usual meaning of stripping typedefs
        }
    }
    
    for (lldb::LanguageType language_type : GetCandidateLanguages(valobj))
    {
        if (Language* language = Language::FindPlugin(language_type))
        {
            for (ConstString candidate : language->GetPossibleFormattersMatches(valobj, use_dynamic))
            {
                entries.push_back({candidate,
                                   reason | lldb_private::eFormatterChoiceCriterionLanguagePlugin,
                                   did_strip_ptr,
                                   did_strip_ref,
                                   did_strip_typedef});
            }
        }
    }

    // try to strip typedef chains
    if (compiler_type.IsTypedefType())
    {
        CompilerType deffed_type = compiler_type.GetTypedefedType();
        GetPossibleMatches(valobj,
                           deffed_type,
                           reason | lldb_private::eFormatterChoiceCriterionNavigatedTypedefs,
                           use_dynamic,
                           entries,
                           did_strip_ptr,
                           did_strip_ref,
                           true);
    }
    
    if (root_level)
    {
        do {
            if (!compiler_type.IsValid())
                break;
            
            CompilerType unqual_compiler_ast_type = compiler_type.GetFullyUnqualifiedType();
            if (!unqual_compiler_ast_type.IsValid())
                break;
            if (unqual_compiler_ast_type.GetOpaqueQualType() != compiler_type.GetOpaqueQualType())
                GetPossibleMatches (valobj,
                                    unqual_compiler_ast_type,
                                    reason,
                                    use_dynamic,
                                    entries,
                                    did_strip_ptr,
                                    did_strip_ref,
                                    did_strip_typedef);
        } while(false);
        
        
        // if all else fails, go to static type
        if (valobj.IsDynamic())
        {
            lldb::ValueObjectSP static_value_sp(valobj.GetStaticValue());
            if (static_value_sp)
                GetPossibleMatches(*static_value_sp.get(),
                                   static_value_sp->GetCompilerType(),
                                   reason | lldb_private::eFormatterChoiceCriterionWentToStaticValue,
                                   use_dynamic,
                                   entries,
                                   did_strip_ptr,
                                   did_strip_ref,
                                   did_strip_typedef,
                                   true);
        }
    }
}

lldb::TypeFormatImplSP
FormatManager::GetFormatForType (lldb::TypeNameSpecifierImplSP type_sp)
{
    if (!type_sp)
        return lldb::TypeFormatImplSP();
    lldb::TypeFormatImplSP format_chosen_sp;
    uint32_t num_categories = m_categories_map.GetCount();
    lldb::TypeCategoryImplSP category_sp;
    uint32_t prio_category = UINT32_MAX;
    for (uint32_t category_id = 0;
         category_id < num_categories;
         category_id++)
    {
        category_sp = GetCategoryAtIndex(category_id);
        if (category_sp->IsEnabled() == false)
            continue;
        lldb::TypeFormatImplSP format_current_sp = category_sp->GetFormatForType(type_sp);
        if (format_current_sp && (format_chosen_sp.get() == NULL || (prio_category > category_sp->GetEnabledPosition())))
        {
            prio_category = category_sp->GetEnabledPosition();
            format_chosen_sp = format_current_sp;
        }
    }
    return format_chosen_sp;
}

lldb::TypeSummaryImplSP
FormatManager::GetSummaryForType (lldb::TypeNameSpecifierImplSP type_sp)
{
    if (!type_sp)
        return lldb::TypeSummaryImplSP();
    lldb::TypeSummaryImplSP summary_chosen_sp;
    uint32_t num_categories = m_categories_map.GetCount();
    lldb::TypeCategoryImplSP category_sp;
    uint32_t prio_category = UINT32_MAX;
    for (uint32_t category_id = 0;
         category_id < num_categories;
         category_id++)
    {
        category_sp = GetCategoryAtIndex(category_id);
        if (category_sp->IsEnabled() == false)
            continue;
        lldb::TypeSummaryImplSP summary_current_sp = category_sp->GetSummaryForType(type_sp);
        if (summary_current_sp && (summary_chosen_sp.get() == NULL || (prio_category > category_sp->GetEnabledPosition())))
        {
            prio_category = category_sp->GetEnabledPosition();
            summary_chosen_sp = summary_current_sp;
        }
    }
    return summary_chosen_sp;
}

lldb::TypeFilterImplSP
FormatManager::GetFilterForType (lldb::TypeNameSpecifierImplSP type_sp)
{
    if (!type_sp)
        return lldb::TypeFilterImplSP();
    lldb::TypeFilterImplSP filter_chosen_sp;
    uint32_t num_categories = m_categories_map.GetCount();
    lldb::TypeCategoryImplSP category_sp;
    uint32_t prio_category = UINT32_MAX;
    for (uint32_t category_id = 0;
         category_id < num_categories;
         category_id++)
    {
        category_sp = GetCategoryAtIndex(category_id);
        if (category_sp->IsEnabled() == false)
            continue;
        lldb::TypeFilterImplSP filter_current_sp((TypeFilterImpl*)category_sp->GetFilterForType(type_sp).get());
        if (filter_current_sp && (filter_chosen_sp.get() == NULL || (prio_category > category_sp->GetEnabledPosition())))
        {
            prio_category = category_sp->GetEnabledPosition();
            filter_chosen_sp = filter_current_sp;
        }
    }
    return filter_chosen_sp;
}

#ifndef LLDB_DISABLE_PYTHON
lldb::ScriptedSyntheticChildrenSP
FormatManager::GetSyntheticForType (lldb::TypeNameSpecifierImplSP type_sp)
{
    if (!type_sp)
        return lldb::ScriptedSyntheticChildrenSP();
    lldb::ScriptedSyntheticChildrenSP synth_chosen_sp;
    uint32_t num_categories = m_categories_map.GetCount();
    lldb::TypeCategoryImplSP category_sp;
    uint32_t prio_category = UINT32_MAX;
    for (uint32_t category_id = 0;
         category_id < num_categories;
         category_id++)
    {
        category_sp = GetCategoryAtIndex(category_id);
        if (category_sp->IsEnabled() == false)
            continue;
        lldb::ScriptedSyntheticChildrenSP synth_current_sp((ScriptedSyntheticChildren*)category_sp->GetSyntheticForType(type_sp).get());
        if (synth_current_sp && (synth_chosen_sp.get() == NULL || (prio_category > category_sp->GetEnabledPosition())))
        {
            prio_category = category_sp->GetEnabledPosition();
            synth_chosen_sp = synth_current_sp;
        }
    }
    return synth_chosen_sp;
}
#endif

#ifndef LLDB_DISABLE_PYTHON
lldb::SyntheticChildrenSP
FormatManager::GetSyntheticChildrenForType (lldb::TypeNameSpecifierImplSP type_sp)
{
    if (!type_sp)
        return lldb::SyntheticChildrenSP();
    lldb::TypeFilterImplSP filter_sp = GetFilterForType(type_sp);
    lldb::ScriptedSyntheticChildrenSP synth_sp = GetSyntheticForType(type_sp);
    if (filter_sp->GetRevision() > synth_sp->GetRevision())
        return lldb::SyntheticChildrenSP(filter_sp.get());
    else
        return lldb::SyntheticChildrenSP(synth_sp.get());
}
#endif

lldb::TypeValidatorImplSP
FormatManager::GetValidatorForType (lldb::TypeNameSpecifierImplSP type_sp)
{
    if (!type_sp)
        return lldb::TypeValidatorImplSP();
    lldb::TypeValidatorImplSP validator_chosen_sp;
    uint32_t num_categories = m_categories_map.GetCount();
    lldb::TypeCategoryImplSP category_sp;
    uint32_t prio_category = UINT32_MAX;
    for (uint32_t category_id = 0;
         category_id < num_categories;
         category_id++)
    {
        category_sp = GetCategoryAtIndex(category_id);
        if (category_sp->IsEnabled() == false)
            continue;
        lldb::TypeValidatorImplSP validator_current_sp(category_sp->GetValidatorForType(type_sp).get());
        if (validator_current_sp && (validator_chosen_sp.get() == NULL || (prio_category > category_sp->GetEnabledPosition())))
        {
            prio_category = category_sp->GetEnabledPosition();
            validator_chosen_sp = validator_current_sp;
        }
    }
    return validator_chosen_sp;
}

void
FormatManager::ForEachCategory(TypeCategoryMap::ForEachCallback callback)
{
    m_categories_map.ForEach(callback);
    Mutex::Locker locker(m_language_categories_mutex);
    for (const auto& entry : m_language_categories_map)
    {
        if (auto category_sp = entry.second->GetCategory())
        {
            if (!callback(category_sp))
                break;
        }
    }
}

lldb::TypeCategoryImplSP
FormatManager::GetCategory (const ConstString& category_name,
                            bool can_create)
{
    if (!category_name)
        return GetCategory(m_default_category_name);
    lldb::TypeCategoryImplSP category;
    if (m_categories_map.Get(category_name, category))
        return category;
    
    if (!can_create)
        return lldb::TypeCategoryImplSP();
    
    m_categories_map.Add(category_name,lldb::TypeCategoryImplSP(new TypeCategoryImpl(this, category_name)));
    return GetCategory(category_name);
}

lldb::Format
FormatManager::GetSingleItemFormat(lldb::Format vector_format)
{
    switch(vector_format)
    {
        case eFormatVectorOfChar:
            return eFormatCharArray;
            
        case eFormatVectorOfSInt8:
        case eFormatVectorOfSInt16:
        case eFormatVectorOfSInt32:
        case eFormatVectorOfSInt64:
            return eFormatDecimal;
            
        case eFormatVectorOfUInt8:
        case eFormatVectorOfUInt16:
        case eFormatVectorOfUInt32:
        case eFormatVectorOfUInt64:
        case eFormatVectorOfUInt128:
            return eFormatHex;
            
        case eFormatVectorOfFloat16:
        case eFormatVectorOfFloat32:
        case eFormatVectorOfFloat64:
            return eFormatFloat;
            
        default:
            return lldb::eFormatInvalid;
    }
}

bool
FormatManager::ShouldPrintAsOneLiner (ValueObject& valobj)
{
    // if settings say no oneline whatsoever
    if (valobj.GetTargetSP().get() && valobj.GetTargetSP()->GetDebugger().GetAutoOneLineSummaries() == false)
        return false; // then don't oneline
    
    // if this object has a summary, then ask the summary
    if (valobj.GetSummaryFormat().get() != nullptr)
        return valobj.GetSummaryFormat()->IsOneLiner();
    
    // no children, no party
    if (valobj.GetNumChildren() == 0)
        return false;
    
    // ask the type if it has any opinion about this
    // eLazyBoolCalculate == no opinion; other values should be self explanatory
    CompilerType compiler_type(valobj.GetCompilerType());
    if (compiler_type.IsValid())
    {
        switch (compiler_type.ShouldPrintAsOneLiner(&valobj))
        {
            case eLazyBoolNo:
                return false;
            case eLazyBoolYes:
                return true;
            case eLazyBoolCalculate:
                break;
        }
    }
    
    size_t total_children_name_len = 0;
    
    for (size_t idx = 0;
         idx < valobj.GetNumChildren();
         idx++)
    {
        bool is_synth_val = false;
        ValueObjectSP child_sp(valobj.GetChildAtIndex(idx, true));
        // something is wrong here - bail out
        if (!child_sp)
            return false;
        
        // also ask the child's type if it has any opinion
        CompilerType child_compiler_type(child_sp->GetCompilerType());
        if (child_compiler_type.IsValid())
        {
            switch (child_compiler_type.ShouldPrintAsOneLiner(child_sp.get()))
            {
                case eLazyBoolYes:
                    // an opinion of yes is only binding for the child, so keep going
                case eLazyBoolCalculate:
                    break;
                case eLazyBoolNo:
                    // but if the child says no, then it's a veto on the whole thing
                    return false;
            }
        }
        
        // if we decided to define synthetic children for a type, we probably care enough
        // to show them, but avoid nesting children in children
        if (child_sp->GetSyntheticChildren().get() != nullptr)
        {
            ValueObjectSP synth_sp(child_sp->GetSyntheticValue());
            // wait.. wat? just get out of here..
            if (!synth_sp)
                return false;
            // but if we only have them to provide a value, keep going
            if (synth_sp->MightHaveChildren() == false && synth_sp->DoesProvideSyntheticValue())
                is_synth_val = true;
            else
                return false;
        }
        
        total_children_name_len += child_sp->GetName().GetLength();
        
        // 50 itself is a "randomly" chosen number - the idea is that
        // overly long structs should not get this treatment
        // FIXME: maybe make this a user-tweakable setting?
        if (total_children_name_len > 50)
            return false;
        
        // if a summary is there..
        if (child_sp->GetSummaryFormat())
        {
            // and it wants children, then bail out
            if (child_sp->GetSummaryFormat()->DoesPrintChildren(child_sp.get()))
                return false;
        }
        
        // if this child has children..
        if (child_sp->GetNumChildren())
        {
            // ...and no summary...
            // (if it had a summary and the summary wanted children, we would have bailed out anyway
            //  so this only makes us bail out if this has no summary and we would then print children)
            if (!child_sp->GetSummaryFormat() && !is_synth_val) // but again only do that if not a synthetic valued child
                return false; // then bail out
        }
    }
    return true;
}

ConstString
FormatManager::GetValidTypeName (const ConstString& type)
{
    return ::GetValidTypeName_Impl(type);
}

ConstString
FormatManager::GetTypeForCache (ValueObject& valobj,
                                lldb::DynamicValueType use_dynamic)
{
    ValueObjectSP valobj_sp = valobj.GetQualifiedRepresentationIfAvailable(use_dynamic, valobj.IsSynthetic());
    if (valobj_sp && valobj_sp->GetCompilerType().IsValid())
    {
        if (!valobj_sp->GetCompilerType().IsMeaninglessWithoutDynamicResolution())
            return valobj_sp->GetQualifiedTypeName();
    }
    return ConstString();
}

std::vector<lldb::LanguageType>
FormatManager::GetCandidateLanguages (ValueObject& valobj)
{
    lldb::LanguageType lang_type = valobj.GetObjectRuntimeLanguage();
    return GetCandidateLanguages(lang_type);
}

std::vector<lldb::LanguageType>
FormatManager::GetCandidateLanguages (lldb::LanguageType lang_type)
{
    switch (lang_type)
    {
        case lldb::eLanguageTypeSwift:
            return {lldb::eLanguageTypeSwift, lldb::eLanguageTypeObjC};
        case lldb::eLanguageTypeObjC:
            return {lldb::eLanguageTypeObjC, lldb::eLanguageTypeSwift};
        case lldb::eLanguageTypeC:
        case lldb::eLanguageTypeC89:
        case lldb::eLanguageTypeC99:
        case lldb::eLanguageTypeC11:
        case lldb::eLanguageTypeC_plus_plus:
        case lldb::eLanguageTypeC_plus_plus_03:
        case lldb::eLanguageTypeC_plus_plus_11:
        case lldb::eLanguageTypeC_plus_plus_14:
            return {lldb::eLanguageTypeC_plus_plus, lldb::eLanguageTypeObjC};
        default:
            return {lang_type};
    }
}

LanguageCategory*
FormatManager::GetCategoryForLanguage (lldb::LanguageType lang_type)
{
    Mutex::Locker locker(m_language_categories_mutex);
    auto iter = m_language_categories_map.find(lang_type), end = m_language_categories_map.end();
    if (iter != end)
        return iter->second.get();
    LanguageCategory* lang_category = new LanguageCategory(lang_type);
    m_language_categories_map[lang_type] = LanguageCategory::UniquePointer(lang_category);
    return lang_category;
}

lldb::TypeFormatImplSP
FormatManager::GetHardcodedFormat (FormattersMatchData& match_data)
{
    TypeFormatImplSP retval_sp;
    
    for (lldb::LanguageType lang_type : match_data.GetCandidateLanguages())
    {
        if (LanguageCategory* lang_category = GetCategoryForLanguage(lang_type))
        {
            if (lang_category->GetHardcoded(*this, match_data, retval_sp))
                break;
        }
    }

    return retval_sp;
}

lldb::TypeFormatImplSP
FormatManager::GetFormat (ValueObject& valobj,
                          lldb::DynamicValueType use_dynamic)
{
    FormattersMatchData match_data(valobj, use_dynamic);
    
    TypeFormatImplSP retval;
    Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_DATAFORMATTERS));
    if (match_data.GetTypeForCache())
    {
        if (log)
            log->Printf("\n\n[FormatManager::GetFormat] Looking into cache for type %s", match_data.GetTypeForCache().AsCString("<invalid>"));
        if (m_format_cache.GetFormat(match_data.GetTypeForCache(),retval))
        {
            if (log)
            {
                log->Printf("[FormatManager::GetFormat] Cache search success. Returning.");
                if (log->GetDebug())
                    log->Printf("[FormatManager::GetFormat] Cache hits: %" PRIu64 " - Cache Misses: %" PRIu64, m_format_cache.GetCacheHits(), m_format_cache.GetCacheMisses());
            }
            return retval;
        }
        if (log)
            log->Printf("[FormatManager::GetFormat] Cache search failed. Going normal route");
    }
    
    retval = m_categories_map.GetFormat(match_data);
    if (!retval)
    {
        if (log)
            log->Printf("[FormatManager::GetFormat] Search failed. Giving language a chance.");
        for (lldb::LanguageType lang_type : match_data.GetCandidateLanguages())
        {
            if (LanguageCategory* lang_category = GetCategoryForLanguage(lang_type))
            {
                if (lang_category->Get(match_data, retval))
                    break;
            }
        }
        if (retval)
        {
            if (log)
                log->Printf("[FormatManager::GetFormat] Language search success. Returning.");
            return retval;
        }
    }
    if (!retval)
    {
        if (log)
            log->Printf("[FormatManager::GetFormat] Search failed. Giving hardcoded a chance.");
        retval = GetHardcodedFormat(match_data);
    }
    
    if (match_data.GetTypeForCache() && (!retval || !retval->NonCacheable()))
    {
        if (log)
            log->Printf("[FormatManager::GetFormat] Caching %p for type %s",
                        static_cast<void*>(retval.get()),
                        match_data.GetTypeForCache().AsCString("<invalid>"));
        m_format_cache.SetFormat(match_data.GetTypeForCache(),retval);
    }
    if (log && log->GetDebug())
        log->Printf("[FormatManager::GetFormat] Cache hits: %" PRIu64 " - Cache Misses: %" PRIu64, m_format_cache.GetCacheHits(), m_format_cache.GetCacheMisses());
    return retval;
}

lldb::TypeSummaryImplSP
FormatManager::GetHardcodedSummaryFormat (FormattersMatchData& match_data)
{
    TypeSummaryImplSP retval_sp;
    
    for (lldb::LanguageType lang_type : match_data.GetCandidateLanguages())
    {
        if (LanguageCategory* lang_category = GetCategoryForLanguage(lang_type))
        {
            if (lang_category->GetHardcoded(*this, match_data, retval_sp))
                break;
        }
    }
    
    return retval_sp;
}

lldb::TypeSummaryImplSP
FormatManager::GetSummaryFormat (ValueObject& valobj,
                                 lldb::DynamicValueType use_dynamic)
{
    FormattersMatchData match_data(valobj, use_dynamic);
    
    TypeSummaryImplSP retval;
    Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_DATAFORMATTERS));
    if (match_data.GetTypeForCache())
    {
        if (log)
            log->Printf("\n\n[FormatManager::GetSummaryFormat] Looking into cache for type %s", match_data.GetTypeForCache().AsCString("<invalid>"));
        if (m_format_cache.GetSummary(match_data.GetTypeForCache(),retval))
        {
            if (log)
            {
                log->Printf("[FormatManager::GetSummaryFormat] Cache search success. Returning.");
                if (log->GetDebug())
                    log->Printf("[FormatManager::GetSummaryFormat] Cache hits: %" PRIu64 " - Cache Misses: %" PRIu64, m_format_cache.GetCacheHits(), m_format_cache.GetCacheMisses());
            }
            return retval;
        }
        if (log)
            log->Printf("[FormatManager::GetSummaryFormat] Cache search failed. Going normal route");
    }
    
    retval = m_categories_map.GetSummaryFormat(match_data);
    if (!retval)
    {
        if (log)
            log->Printf("[FormatManager::GetSummaryFormat] Search failed. Giving language a chance.");
        for (lldb::LanguageType lang_type : match_data.GetCandidateLanguages())
        {
            if (LanguageCategory* lang_category = GetCategoryForLanguage(lang_type))
            {
                if (lang_category->Get(match_data, retval))
                    break;
            }
        }
        if (retval)
        {
            if (log)
                log->Printf("[FormatManager::GetSummaryFormat] Language search success. Returning.");
            return retval;
        }
    }
    if (!retval)
    {
        if (log)
            log->Printf("[FormatManager::GetSummaryFormat] Search failed. Giving hardcoded a chance.");
        retval = GetHardcodedSummaryFormat(match_data);
    }
    
    if (match_data.GetTypeForCache() && (!retval || !retval->NonCacheable()))
    {
        if (log)
            log->Printf("[FormatManager::GetSummaryFormat] Caching %p for type %s",
                        static_cast<void*>(retval.get()),
                        match_data.GetTypeForCache().AsCString("<invalid>"));
        m_format_cache.SetSummary(match_data.GetTypeForCache(),retval);
    }
    if (log && log->GetDebug())
        log->Printf("[FormatManager::GetSummaryFormat] Cache hits: %" PRIu64 " - Cache Misses: %" PRIu64, m_format_cache.GetCacheHits(), m_format_cache.GetCacheMisses());
    return retval;
}

#ifndef LLDB_DISABLE_PYTHON
lldb::SyntheticChildrenSP
FormatManager::GetHardcodedSyntheticChildren (FormattersMatchData& match_data)
{
    SyntheticChildrenSP retval_sp;
    
    for (lldb::LanguageType lang_type : match_data.GetCandidateLanguages())
    {
        if (LanguageCategory* lang_category = GetCategoryForLanguage(lang_type))
        {
            if (lang_category->GetHardcoded(*this, match_data, retval_sp))
                break;
        }
    }
    
    return retval_sp;
}

lldb::SyntheticChildrenSP
FormatManager::GetSyntheticChildren (ValueObject& valobj,
                                     lldb::DynamicValueType use_dynamic)
{
    FormattersMatchData match_data(valobj, use_dynamic);
    
    SyntheticChildrenSP retval;
    Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_DATAFORMATTERS));
    if (match_data.GetTypeForCache())
    {
        if (log)
            log->Printf("\n\n[FormatManager::GetSyntheticChildren] Looking into cache for type %s", match_data.GetTypeForCache().AsCString("<invalid>"));
        if (m_format_cache.GetSynthetic(match_data.GetTypeForCache(),retval))
        {
            if (log)
            {
                log->Printf("[FormatManager::GetSyntheticChildren] Cache search success. Returning.");
                if (log->GetDebug())
                    log->Printf("[FormatManager::GetSyntheticChildren] Cache hits: %" PRIu64 " - Cache Misses: %" PRIu64, m_format_cache.GetCacheHits(), m_format_cache.GetCacheMisses());
            }
            return retval;
        }
        if (log)
            log->Printf("[FormatManager::GetSyntheticChildren] Cache search failed. Going normal route");
    }
    
    retval = m_categories_map.GetSyntheticChildren(match_data);
    if (!retval)
    {
        if (log)
            log->Printf("[FormatManager::GetSyntheticChildren] Search failed. Giving language a chance.");
        for (lldb::LanguageType lang_type : match_data.GetCandidateLanguages())
        {
            if (LanguageCategory* lang_category = GetCategoryForLanguage(lang_type))
            {
                if (lang_category->Get(match_data, retval))
                    break;
            }
        }
        if (retval)
        {
            if (log)
                log->Printf("[FormatManager::GetSyntheticChildren] Language search success. Returning.");
            return retval;
        }
    }
    if (!retval)
    {
        if (log)
            log->Printf("[FormatManager::GetSyntheticChildren] Search failed. Giving hardcoded a chance.");
        retval = GetHardcodedSyntheticChildren(match_data);
    }
    
    if (match_data.GetTypeForCache() && (!retval || !retval->NonCacheable()))
    {
        if (log)
            log->Printf("[FormatManager::GetSyntheticChildren] Caching %p for type %s",
                        static_cast<void*>(retval.get()),
                        match_data.GetTypeForCache().AsCString("<invalid>"));
        m_format_cache.SetSynthetic(match_data.GetTypeForCache(),retval);
    }
    if (log && log->GetDebug())
        log->Printf("[FormatManager::GetSyntheticChildren] Cache hits: %" PRIu64 " - Cache Misses: %" PRIu64, m_format_cache.GetCacheHits(), m_format_cache.GetCacheMisses());
    return retval;
}
#endif

lldb::TypeValidatorImplSP
FormatManager::GetValidator (ValueObject& valobj,
                             lldb::DynamicValueType use_dynamic)
{
    FormattersMatchData match_data(valobj, use_dynamic);
    
    TypeValidatorImplSP retval;
    Log *log(lldb_private::GetLogIfAllCategoriesSet (LIBLLDB_LOG_DATAFORMATTERS));
    if (match_data.GetTypeForCache())
    {
        if (log)
            log->Printf("\n\n[FormatManager::GetValidator] Looking into cache for type %s", match_data.GetTypeForCache().AsCString("<invalid>"));
        if (m_format_cache.GetValidator(match_data.GetTypeForCache(),retval))
        {
            if (log)
            {
                log->Printf("[FormatManager::GetValidator] Cache search success. Returning.");
                if (log->GetDebug())
                    log->Printf("[FormatManager::GetValidator] Cache hits: %" PRIu64 " - Cache Misses: %" PRIu64, m_format_cache.GetCacheHits(), m_format_cache.GetCacheMisses());
            }
            return retval;
        }
        if (log)
            log->Printf("[FormatManager::GetValidator] Cache search failed. Going normal route");
    }
    
    retval = m_categories_map.GetValidator(match_data);
    if (!retval)
    {
        if (log)
            log->Printf("[FormatManager::GetValidator] Search failed. Giving language a chance.");
        for (lldb::LanguageType lang_type : match_data.GetCandidateLanguages())
        {
            if (LanguageCategory* lang_category = GetCategoryForLanguage(lang_type))
            {
                if (lang_category->Get(match_data, retval))
                    break;
            }
        }
        if (retval)
        {
            if (log)
                log->Printf("[FormatManager::GetValidator] Language search success. Returning.");
            return retval;
        }
    }
    if (!retval)
    {
        if (log)
            log->Printf("[FormatManager::GetValidator] Search failed. Giving hardcoded a chance.");
        retval = GetHardcodedValidator(match_data);
    }
    
    if (match_data.GetTypeForCache() && (!retval || !retval->NonCacheable()))
    {
        if (log)
            log->Printf("[FormatManager::GetValidator] Caching %p for type %s",
                        static_cast<void*>(retval.get()),
                        match_data.GetTypeForCache().AsCString("<invalid>"));
        m_format_cache.SetValidator(match_data.GetTypeForCache(),retval);
    }
    if (log && log->GetDebug())
        log->Printf("[FormatManager::GetValidator] Cache hits: %" PRIu64 " - Cache Misses: %" PRIu64, m_format_cache.GetCacheHits(), m_format_cache.GetCacheMisses());
    return retval;
}

lldb::TypeValidatorImplSP
FormatManager::GetHardcodedValidator (FormattersMatchData& match_data)
{
    TypeValidatorImplSP retval_sp;
    
    for (lldb::LanguageType lang_type : match_data.GetCandidateLanguages())
    {
        if (LanguageCategory* lang_category = GetCategoryForLanguage(lang_type))
        {
            if (lang_category->GetHardcoded(*this, match_data, retval_sp))
                break;
        }
    }
    
    return retval_sp;
}

FormatManager::FormatManager() :
    m_last_revision(0),
    m_format_cache(),
    m_language_categories_mutex(Mutex::eMutexTypeRecursive),
    m_language_categories_map(),
    m_named_summaries_map(this),
    m_categories_map(this),
    m_default_category_name(ConstString("default")),
    m_system_category_name(ConstString("system")), 
    m_vectortypes_category_name(ConstString("VectorTypes")),
    m_runtime_synths_category_name(ConstString("runtime-synthetics"))
{
    LoadSystemFormatters();
    LoadVectorFormatters();
    
    GetCategory(m_runtime_synths_category_name); // EnableCategory() won't enable a non-existant category, so create this one first even if empty
    
    EnableCategory(m_vectortypes_category_name,TypeCategoryMap::Last, lldb::eLanguageTypeObjC_plus_plus);
    EnableCategory(m_runtime_synths_category_name,TypeCategoryMap::Last, {lldb::eLanguageTypeObjC_plus_plus,lldb::eLanguageTypeSwift});
    EnableCategory(m_system_category_name,TypeCategoryMap::Last, lldb::eLanguageTypeObjC_plus_plus);
}

void
FormatManager::LoadSystemFormatters()
{
    TypeSummaryImpl::Flags string_flags;
    string_flags.SetCascades(true)
    .SetSkipPointers(true)
    .SetSkipReferences(false)
    .SetDontShowChildren(true)
    .SetDontShowValue(false)
    .SetShowMembersOneLiner(false)
    .SetHideItemNames(false);
    
    TypeSummaryImpl::Flags string_array_flags;
    string_array_flags.SetCascades(true)
    .SetSkipPointers(true)
    .SetSkipReferences(false)
    .SetDontShowChildren(true)
    .SetDontShowValue(true)
    .SetShowMembersOneLiner(false)
    .SetHideItemNames(false);
    
    lldb::TypeSummaryImplSP string_format(new StringSummaryFormat(string_flags, "${var%s}"));
    
    
    lldb::TypeSummaryImplSP string_array_format(new StringSummaryFormat(string_array_flags,
                                                                        "${var%s}"));
    
    lldb::RegularExpressionSP any_size_char_arr(new RegularExpression("char \\[[0-9]+\\]"));
    lldb::RegularExpressionSP any_size_wchar_arr(new RegularExpression("wchar_t \\[[0-9]+\\]"));
    
    TypeCategoryImpl::SharedPointer sys_category_sp = GetCategory(m_system_category_name);
    
    sys_category_sp->GetTypeSummariesContainer()->Add(ConstString("char *"), string_format);
    sys_category_sp->GetTypeSummariesContainer()->Add(ConstString("unsigned char *"), string_format);
    sys_category_sp->GetRegexTypeSummariesContainer()->Add(any_size_char_arr, string_array_format);

    lldb::TypeSummaryImplSP ostype_summary(new StringSummaryFormat(TypeSummaryImpl::Flags().SetCascades(false)
                                                                   .SetSkipPointers(true)
                                                                   .SetSkipReferences(true)
                                                                   .SetDontShowChildren(true)
                                                                   .SetDontShowValue(false)
                                                                   .SetShowMembersOneLiner(false)
                                                                   .SetHideItemNames(false),
                                                                   "${var%O}"));
    
    sys_category_sp->GetTypeSummariesContainer()->Add(ConstString("OSType"), ostype_summary);
    
#ifndef LLDB_DISABLE_PYTHON
    TypeFormatImpl::Flags fourchar_flags;
    fourchar_flags.SetCascades(true).SetSkipPointers(true).SetSkipReferences(true);
    
    AddFormat(sys_category_sp, lldb::eFormatOSType, ConstString("FourCharCode"), fourchar_flags);
#endif
}

void
FormatManager::LoadVectorFormatters()
{
    TypeCategoryImpl::SharedPointer vectors_category_sp = GetCategory(m_vectortypes_category_name);
    
    TypeSummaryImpl::Flags vector_flags;
    vector_flags.SetCascades(true)
    .SetSkipPointers(true)
    .SetSkipReferences(false)
    .SetDontShowChildren(true)
    .SetDontShowValue(false)
    .SetShowMembersOneLiner(true)
    .SetHideItemNames(true);
    
    AddStringSummary(vectors_category_sp,
                     "${var.uint128}",
                     ConstString("builtin_type_vec128"),
                     vector_flags);
    
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("float [4]"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("int32_t [4]"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("int16_t [8]"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vDouble"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vFloat"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vSInt8"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vSInt16"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vSInt32"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vUInt16"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vUInt8"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vUInt16"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vUInt32"),
                     vector_flags);
    AddStringSummary(vectors_category_sp,
                     "",
                     ConstString("vBool32"),
                     vector_flags);
}
